# -*- coding: utf-8 -*-
"""
区服跨服共用的角色信息缓存

Module Description: 角色信息缓存
Date: 2020/09/28
Author: sxj
"""
from collections import defaultdict
from itertools import chain

from pyshare.utils.dict_utils import chunks
from pyshare.core.context import temporary_ctx
from pyshare.dal.repository_impl import RoleRepository, AllianceRepository, MinisterRepository, \
    CrossAlliesRoleRepository, CrossAlliesCountryRepository, CrossAlliesRepository, CrossSeasonRoleRepository, \
    EmperorRepository
from pyshare.redis_cache.share.share_role_cache import ShareRoleInfoCache
from pyshare.bll.utils.share.allies_information import AlliesCountryCache
from pyshare.bll.utils.share.base_cache_info import BaseCacheInfo
from pyshare.utils.season_utils import get_last_season_id
from pyshare.framework import runtime_logger as log


class RoleInfo(BaseCacheInfo):
    default_var = {
        # 联盟名
        'alliance_name': "",
        # 所有大臣总战力和
        'sum_fight_power': 0,
        # 上赛季段位
        'last_season_dan': 0,
        # 玩家所处国家信息
        'allies_id': "", 'country_name': "", 'country_id': 0, 'allies_no': 0, 'name_change_times': 0,
        # 角色基本信息
        # 实时刷新字段: nick_name, small_head_id, head_frame_id, head_id, last_login_time
        'nick_name': "", 'head_id': 1, 'vip_lv': 0,  'role_guid': "", 'srv_id': 0, 'role_lv': 1, 'small_head_id': 1,
        'head_frame_id': 1, 'privacy_setting': {}, 'service_id': 0, 'last_login_time': "2000-01-01 00:00:00", 'sex': 1,
        'saint_medal_lv': 0, 'is_adorn_saint_medal': 0,
        # 角色当前时装染色搭配
        'clothes_color': []
    }


# 角色信息缓存列表
RoleInfoList = [RoleInfo]


class ShareRoleInformation(object):
    """
    角色信息
    """

    max_prepared_count = 500  # 每次从数据库获取的最大数目
    role_info_cache = ShareRoleInfoCache()

    @classmethod
    def get_role_dict(cls, role, alliance_dict, minister_data, info_index=0):
        """
        获取存入缓存角色信息字典
        :param role: TbRole对象实例
        :param alliance_dict: 联盟信息字典
        :param minister_data: 大臣信息
        :param info_index: RoleInfoList下标
        注：新增加了字段需要同步到get_role_information_from_db方法
        :return:
        """
        # 获取同盟国国家信息
        allies_id, country_name, country_id, allies_no, name_change_times, clothes_color = '', '', 0, 0, 0, []
        allies_role = CrossAlliesRoleRepository.get_by_rid(rid=role.role_guid)
        if allies_role:
            allies_id = allies_role.allies_id
            if allies_role.country_id:
                country_id = allies_role.country_id

            country_info = AlliesCountryCache.get_country_info_from_cache_db(allies_id, country_id)
            if country_info:
                country_name = country_info.CountryName
                allies_no = country_info.AlliesNo
                name_change_times = country_info.NameChangeTimes

        # 获取玩家上赛季段位
        last_season_id = get_last_season_id()
        last_season_dan = 0
        if last_season_id:
            dan_dict = cls.get_many_player_last_season_dan([role.role_guid], last_season_id)
            if dan_dict:
                last_season_dan = dan_dict.get(role.role_guid, 0)

        # 获取玩家此时身上时装的颜色搭配
        emperor_role = EmperorRepository.get_by_rid(role.role_guid)
        if emperor_role:
            clothes_color = emperor_role.clothes_color

        role_dict = dict()
        fields = RoleInfoList[info_index]().get_fields()
        for field in fields:
            if field == 'alliance_name':
                role_dict[field] = alliance_dict.get('Name', '')
            elif field == 'sum_fight_power':
                fight_power = [m.get('FightPower', 0) for m in minister_data]
                sum_fighter_power = sum(fight_power)
                role_dict[field] = sum_fighter_power
            elif field == 'last_season_dan':
                role_dict[field] = last_season_dan
            elif field == 'allies_id':
                role_dict[field] = allies_id
            elif field == 'country_name':
                role_dict[field] = country_name
            elif field == 'country_id':
                role_dict[field] = country_id
            elif field == 'allies_no':
                role_dict[field] = allies_no
            elif field == 'name_change_times':
                role_dict[field] = name_change_times
            elif field == 'clothes_color':
                role_dict[field] = clothes_color
            else:
                role_dict[field] = getattr(role, field)
        return role_dict

    @classmethod
    def refresh_role_cache_login(cls, rid, role, alliance_dict, minister_data):
        """
        登录刷新角色信息缓存
        :param rid: 角色rid
        :param role: tb_role实例
        :param alliance_dict: 联盟信息
        :param minister_data: 大臣信息
        :return:
        """
        server_id = 0
        try:
            server_id = role.srv_id
            role_dict = cls.get_role_dict(role, alliance_dict, minister_data)
            cls.set_role_information(rid, role_dict)
        except Exception as e:
            from pyshare.utils.traceback_utils import print_exc
            log.warning('Rid: {}, ServerId: {}, Login refresh cache error: \n {}'.format(rid, server_id, print_exc(e)))

    @staticmethod
    def get_many_player_alliance_name(rid_list, **kwargs):
        """
        获取角色所属联盟
        :param rid_list:
        :return:
        """
        alliance_dict = {r['RoleGuid']: r['Name'] for r in AllianceRepository.filter_name_by_rid_list(rid_list)}
        rid_alliance_name_dict = {}
        for rid in rid_list:
            rid_alliance_name_dict[rid] = alliance_dict.get(rid, '')

        return rid_alliance_name_dict

    @staticmethod
    def get_many_player_sum_fight_power(rid_list):
        """
        获取角色大臣战力总和
        :param rid_list:
        :return:
        """
        role_dict = {m['RoleGuid']: int(m['SumFightPower']) for m in MinisterRepository.filter_sum_fighter_power(rid_list)}
        rid_fight_power = {}
        for rid in rid_list:
            rid_fight_power[rid] = role_dict.get(rid, 0)
        return rid_fight_power

    @staticmethod
    def get_many_player_allies_info(rid_list):
        """
        获取角色同盟国国家信息
        :param rid_list:
        :return:
        """
        role_info = dict()
        allies_id_list = list()
        rid_allies_info = dict()
        allies_country_dict = dict()  # {'allies_country_id': {country_info}}
        allies_role_list = CrossAlliesRoleRepository.filter_by_rid_list(rid_list)
        # 获取allies_id和国家id
        for allies_role in allies_role_list:
            rid_allies_info[allies_role.role_guid] = {
                'AlliesId': allies_role.allies_id,
                'CountryId': allies_role.country_id
            }
            allies_id_list.append(allies_role.allies_id)

        # 去重
        allies_id_list = list(set(allies_id_list))

        # 获取allies_no
        allies_list = CrossAlliesRepository.filter_by_allies_id_list(allies_id_list, 'AlliesNo')
        allies_id_no_dict = {a.allies_id: a.allies_no for a in allies_list}

        # 获取国家信息
        country_list = CrossAlliesCountryRepository.filter_by_allies_id_list(allies_id_list, 'AlliesId', 'CountryName',
                                                                             'CountryId', 'NameChangeTimes')
        for country in country_list:
            a_c_id = country.allies_id + '_' + str(country.country_id)
            allies_country_dict[a_c_id] = country

        # 拼接最终需要的信息
        for rid in rid_list:
            allies_id, country_name, country_id, allies_no, name_change_times = '', '', 0, 0, 0
            allies_role_info = rid_allies_info.get(rid, {})
            if allies_role_info:
                allies_id, country_id = allies_role_info.get('AlliesId', ''), allies_role_info.get('CountryId', 0)
                if not country_id:
                    country_id = 0
                allies_no = allies_id_no_dict.get(allies_id, 0)
            if country_id:
                a_c_id = allies_id + '_' + str(country_id)
                country_info = allies_country_dict.get(a_c_id, None)
                if country_info:
                    country_name = country_info.country_name
                    name_change_times = country_info.name_change_times

            role_dict = {
                "RoleGuid": str(rid),
                "AlliesId": str(allies_id),
                "CountryId": country_id,
                "AlliesNo": allies_no,
                "CountryName": str(country_name),
                "NameChangeTimes": int(name_change_times)
            }

            role_info.update({rid: role_dict})
        return role_info

    @staticmethod
    def get_many_player_last_season_dan(rid_list, last_season_id):
        """
        获取玩家上赛季段位
        :param rid_list:
        :param last_season_id: 上赛季id
        :return:
        """
        dan_dict = {}
        if last_season_id and last_season_id > 0:
            dan_dict = {role.role_guid: role.dan for role in
                        CrossSeasonRoleRepository.filter_by_rid_list_season_id(rid_list, last_season_id, 'RoleGuid',
                                                                               'Dan')}
        return dan_dict

    @classmethod
    def get_role_information_from_db(cls, server_dict, need_alliance=True, need_fight_power=True, need_allies=True,
                                     need_dan=True, info_index=0, **kwargs):
        """
        获取多区服角色信息 昵称/所属联盟
        :param server_dict: {server_id: rid_list}
        :param need_alliance: 是否需要联盟信息
        :param need_allies: 是否需要所属同盟国国家信息
        :param need_dan: 是否需要玩家上赛季段位
        :param info_index: 角色信息存储RoleInfoList下标
        :return: {rid: RoleInfoList[info_index]}
        """
        role_dict, role_allies_info, role_dan_info = {}, {}, {}
        all_rid_list = []
        for rid_list in server_dict.values():
            all_rid_list.extend(rid_list)

        last_season_id = None
        if need_dan:
            # 获取上赛季id
            last_season_id = get_last_season_id()

        # 跨服信息获取
        for rid_short_list in chunks(all_rid_list, cls.max_prepared_count):
            if need_allies:
                # 获取国家信息（由于国家数据切战区获取，所以不切片处理）
                allies_info = cls.get_many_player_allies_info(rid_short_list)
                role_allies_info.update(allies_info)
            if need_dan and last_season_id:
                # 获取玩家上赛季段位
                dan_info = cls.get_many_player_last_season_dan(rid_short_list, last_season_id)
                role_dan_info.update(dan_info)

        for server_id, rid_list in server_dict.iteritems():
            if not rid_list:
                continue

            # 指定访问的区服
            with temporary_ctx(server_id=server_id):
                # 获取昵称
                role_list = list()
                role_alliance_name = {}
                role_fight_power = {}
                for rid_short_list in chunks(rid_list, cls.max_prepared_count):
                    rid_short_list = [str(rid) for rid in rid_short_list]
                    # 获取角色信息
                    role_short_list = RoleRepository.filter_by_rid_list(rid_short_list, 'RoleGuid', 'SmallHeadId',
                                                                        'HeadId', 'NickName', 'VipLv', 'RoleLv',
                                                                        'HeadFrameId', 'PrivacySetting', 'ServiceId',
                                                                        'SrvId', 'LastLoginTime', 'Sex', 'SaintMedalLv',
                                                                        'IsAdornSaintMedal')
                    role_list.extend(role_short_list)
                    role_emperor = {role.role_guid: role.clothes_color for role in EmperorRepository.filter_by_rid_list(
                        rid_short_list, 'RoleGuid', 'ClothesColor')}

                    # 获取所属联盟
                    if need_alliance:
                        alliance_name = cls.get_many_player_alliance_name(rid_short_list, **kwargs)
                        role_alliance_name.update(alliance_name)

                    # 获取玩家所有大臣总战力
                    if need_fight_power:
                        fighter_power = cls.get_many_player_sum_fight_power(rid_short_list)
                        role_fight_power.update(fighter_power)

                fields = RoleInfoList[info_index]().get_fields()
                for role in role_list:
                    role_i = {}
                    allies_info = role_allies_info.get(role.role_guid, {})
                    for field in fields:
                        if field == 'alliance_name':
                            role_i[field] = role_alliance_name.get(role.role_guid, '')
                        elif field == 'sum_fight_power':
                            role_i[field] = role_fight_power.get(role.role_guid, 0)
                        elif field == 'last_season_dan':
                            role_i[field] = role_dan_info.get(role.role_guid, 0)
                        elif field == 'allies_id':
                            role_i[field] = allies_info.get('AlliesId', '')
                        elif field == 'country_name':
                            role_i[field] = allies_info.get('CountryName', '')
                        elif field == 'country_id':
                            role_i[field] = allies_info.get('CountryId', 0)
                        elif field == 'allies_no':
                            role_i[field] = allies_info.get('AlliesNo', 0)
                        elif field == 'name_change_times':
                            role_i[field] = allies_info.get('NameChangeTimes', 0)
                        elif field == 'clothes_color':
                            role_i[field] = role_emperor.get(role.role_guid, [])
                        else:
                            role_i[field] = getattr(role, field)
                    role_dict[role.role_guid] = RoleInfoList[info_index](**role_i)

        return role_dict

    @classmethod
    def set_role_information(cls, rid, role_dict):
        """
        角色信息写入缓存
        :param rid:
        :param role_dict:
        :return:
        """
        if not role_dict:
            return

        cls.role_info_cache.set_role_cache(rid, role_dict, need_dumps=True)

    @classmethod
    def set_many_role_information(cls, information_dict):
        """
        批量角色信息写入缓存
        :param information_dict: {rid: {role_data_dict}}
        :return:
        """
        if not information_dict:
            return

        cls.role_info_cache.set_many_role_cache(information_dict, need_dumps=True)

    @classmethod
    def get_role_information_from_cache(cls, rid, info_index=0):
        """
        从缓存获取角色信息
        若缓存中无对应信息，则返回空字典{}
        :param rid:
        :param info_index: 角色信息存储RoleInfoList下标
        :return: RoleInfoList[info_index]实例
        """
        role_dict = cls.role_info_cache.get_role_cache(rid, need_loads=True)
        if not role_dict:
            return {}
        return RoleInfoList[info_index](**role_dict)

    @classmethod
    def get_role_information_from_cache_db(cls, rid, server_id, info_index=0, need_last_dan_from_db=False,
                                           need_allies_cache=False):
        """
        从缓存获取角色信息
        若缓存无对应信息，则从db获取，并将信息写入缓存
        :param need_allies_cache: 是否需要读取国家缓存
        :param rid:
        :param server_id:
        :param info_index: 角色信息存储RoleInfoList下标
        :param need_last_dan_from_db: 是否从数据库读取上赛季段位
        :return: RoleInfoList[info_index]实例
        """
        if (not rid) or (not server_id):
            return None
        role_dan_info = dict()
        role_dict = cls.role_info_cache.get_role_cache(rid, need_loads=True)
        # 是否从数据库拿上个赛季段位 TODO（只适合活动相关获取，源数据涉及到清理问题）
        if need_last_dan_from_db:
            # 获取上赛季id
            last_season_id = get_last_season_id()
            # 跨服信息获取
            # 获取玩家上赛季段位
            dan_info = cls.get_many_player_last_season_dan([rid], last_season_id)
            role_dan_info.update(dan_info)
        if role_dict:
            role_info = RoleInfoList[info_index](**role_dict)
            if need_last_dan_from_db:
                role_info.last_season_dan = role_dan_info.get(rid, 0)
            if need_allies_cache:
                country_info = AlliesCountryCache.get_country_info_from_cache_db(role_info.allies_id,
                                                                                 role_info.country_id)

                role_info.country_name = country_info.CountryName if country_info else ""
                role_info.name_change_times = country_info.NameChangeTimes if country_info else 0
                role_info.allies_no = country_info.AlliesNo if country_info else 0
            return role_info

        # 缓存中获取失败，则从DB读取并写入缓存
        server_dict = {server_id: [rid]}
        role_info_dict = cls.get_role_information_from_db(server_dict, need_alliance=True)
        # 写入缓存
        if role_info_dict:
            cls.role_info_cache.set_role_cache(rid, role_info_dict[rid]._asdict(), need_dumps=True)
        return role_info_dict.get(rid, {})

    @classmethod
    def get_many_role_information_from_cache(cls, rid_list, info_index=0):
        """
        从缓存批量获取角色信息
        若缓存中无对应信息，则将rid记录在not_found_list中返回
        :param rid_list:
        :param info_index: 角色信息存储RoleInfoList下标
        :return: info_dict: {rid: RoleInfoList[info_index]实例}, not_found_list: [rid1, rid2...]
        """
        info_dict = dict()
        not_found_list = list()
        if not rid_list:
            return info_dict, not_found_list
        role_info_list = cls.role_info_cache.get_many_role_cache(rid_list, need_loads=True)
        for rid, info in zip(rid_list, role_info_list):
            if info:
                info_dict.update({rid: RoleInfoList[info_index](**info)})
            else:
                not_found_list.append(rid)
        return info_dict, not_found_list

    @classmethod
    def get_many_role_information_from_cache_db(cls, serverid_rid_dict, info_index=0, need_last_dan_from_db=False,
                                                need_allies_cache=False):
        """
        从缓存批量获取角色信息
        若缓存中无对应信息，则从db获取，并写入缓存
        :param need_allies_cache: 是否需要国家缓存
        :param serverid_rid_dict: {server_id: [rid1, rid2...]}
        :param info_index: 角色信息存储RoleInfoList下标
        :param need_last_dan_from_db: 是否需要从DB获取上个赛季段位
        :return: {rid: RoleInfoList[info_index]实例}}
        """
        info_dict = dict()
        not_found_list = list()
        role_dan_info = dict()
        country_info_dict = dict()
        allies_dict = defaultdict(list)
        all_rid_list = list(set(chain(*serverid_rid_dict.values())))
        if not all_rid_list:
            return info_dict
        role_info_list = cls.role_info_cache.get_many_role_cache(all_rid_list, need_loads=True)
        # 是否从数据库拿上个赛季段位 TODO（只适合活动相关获取，源数据涉及到清理问题）
        if need_last_dan_from_db:
            # 获取上赛季id
            last_season_id = get_last_season_id()
            # 跨服信息获取
            for rid_short_list in chunks(all_rid_list, cls.max_prepared_count):
                # 获取玩家上赛季段位
                dan_info = cls.get_many_player_last_season_dan(rid_short_list, last_season_id)
                role_dan_info.update(dan_info)

        if need_allies_cache:
            for rid, info in zip(all_rid_list, role_info_list):
                if info:
                    role_info = RoleInfoList[info_index](**info)
                    allies_dict[role_info.allies_id].append(role_info.country_id)
            country_info_dict = AlliesCountryCache.get_many_country_info_from_cache_db(allies_dict)

        for rid, info in zip(all_rid_list, role_info_list):
            if info:
                role_info = RoleInfoList[info_index](**info)
                if need_last_dan_from_db:
                    role_info.last_season_dan = role_dan_info.get(rid, 0)
                if need_allies_cache:
                    a_c_id = AlliesCountryCache.get_key(role_info.allies_id, role_info.country_id)
                    country_info = country_info_dict.get(a_c_id, None)
                    role_info.country_name = country_info.CountryName if country_info else ""
                    role_info.name_change_times = country_info.NameChangeTimes if country_info else 0
                    role_info.allies_no = country_info.AlliesNo if country_info else 0
                info_dict.update({rid: role_info})
            else:
                not_found_list.append(rid)

        # 从缓存获取失败的角色，从db获取
        for server_id, rid_list in serverid_rid_dict.iteritems():
            if not rid_list:
                continue

            not_find_rid_set = set(not_found_list) & set(rid_list)
            if not not_find_rid_set:
                continue

            info_dict_from_db = cls.get_role_information_from_db({int(server_id): list(not_find_rid_set)},
                                                                 need_alliance=True)
            # 写入缓存
            if info_dict_from_db:
                info_dict.update(info_dict_from_db)
                tmp_dict = dict()
                for k, v in info_dict_from_db.items():
                    tmp_dict[k] = v._asdict()
                cls.role_info_cache.set_many_role_cache(tmp_dict, need_dumps=True)

        return info_dict

    @classmethod
    def delete_role_cache(cls, rid):
        """
        删除缓存
        :param rid: 角色rid
        :return:
        """
        cls.role_info_cache.delete_role_cache(rid)

    @classmethod
    def get_many_role_dan_from_db(cls, rid_list, season_id=None):
        """
        从数据库获取段位信息（实时数据，缓存里的段位信息，实时性更低）
        :param rid_list: 角色rid列表
        :param season_id: 查询的赛季id
        :return：{rid: dan}
        """
        role_dan_info = {}
        if not season_id:
            # 默认不传的话，获取上赛季id
            season_id = get_last_season_id()
        # 跨服信息获取
        for rid_short_list in chunks(rid_list, cls.max_prepared_count):
            # 获取玩家上赛季段位
            dan_info = cls.get_many_player_last_season_dan(rid_short_list, season_id)
            role_dan_info.update(dan_info)
        return role_dan_info
